﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reactive;
using System.Reactive.Linq;
using System.Windows;

namespace Present.Commands.Rx
{
    public static class NotifyOnChangeExtensions
    {
        /// <summary>
        /// Call a delegate when a particualr property changes
        /// </summary>
        public static IObservable<EventPattern<PropertyChangedEventArgs>> WhenPropertyChanges<T>(this T me, Expression<Func<T, object>> property) where T : INotifyPropertyChanged
        {
            return me.FromPropertyChangedQuick().Where(e => e.EventArgs.PropertyName == property.GetNameFromExpression());
        }



        /// <summary>
        /// Call a delegate when any of a particualr property changes
        /// </summary>
        public static IObservable<EventPattern<PropertyChangedEventArgs>> WhenPropertiesChange<T>(this T me, params Expression<Func<T, object>>[] properties) where T : INotifyPropertyChanged
        {

            if (properties == null || properties.Length == 0)
                throw new ArgumentException("properties must not be empty");

            return properties.Select(p => me.WhenPropertyChanges(p)).Merge();
        }


        /// <summary>
        /// Call a delegate when a particualr property changes
        /// </summary>
        public static IObservable<EventPattern<EventArgs>> WhenDependencyPropertyChanges<T>(this T me, Expression<Func<T, object>> property) where T : DependencyObject
        {
            return FromPropertyChanged(me, property);
        }


        /// <summary>
        /// Whilst less flexible. This is quicker which helps speed up things like tree generation.
        /// </summary>
        private static IObservable<EventPattern<PropertyChangedEventArgs>> FromPropertyChangedQuick(this INotifyPropertyChanged me)
        {
            return Observable
               .FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
                   h => new PropertyChangedEventHandler(h),
                   h => me.PropertyChanged += h,
                   h => me.PropertyChanged -= h
               );
        }


        private static IObservable<EventPattern<EventArgs>> FromPropertyChanged<T>(T target, Expression<Func<T, object>> property)
        {
            Guard.IsNotNull(target, "target");
            Guard.IsNotNull(property, "property");


            string propertyName = property.GetNameFromExpression();

            var propertyDescriptor = (from p in TypeDescriptor.GetProperties(target).Cast<PropertyDescriptor>()
                                      where string.Equals(p.Name, propertyName, StringComparison.Ordinal)
                                      select p)
                                      .Single();

            if (!propertyDescriptor.SupportsChangeEvents)
                throw new ArgumentException(string.Format("The specified property '{0}' does not support change events.", propertyName), "property");


            return Observable.FromEventPattern<EventHandler, EventArgs>(
                d => d.Invoke,
                h => propertyDescriptor.AddValueChanged(target, h),
                h => propertyDescriptor.RemoveValueChanged(target, h));
        }




    }
}
